package com.bumptech.glide.util.pool;

import com.bumptech.glide.util.LogUtil;
import com.bumptech.glide.util.pool.Pools.Pool;
import com.bumptech.glide.util.pool.Pools.SimplePool;
import com.bumptech.glide.util.pool.Pools.SynchronizedPool;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.List;

/**
 * Provides implementations of {@link Pool} never return {@code null}, log when new instances are
 * created, and that can use the {@link com.bumptech.glide.util.pool.FactoryPools.Poolable}
 * interface to ensure objects aren't used while inside the pool.
 */
public final class FactoryPools {
  private static final String TAG = "FactoryPools";
  private static final int DEFAULT_POOL_SIZE = 20;
  private static final Resetter<Object> EMPTY_RESETTER =
      new Resetter<Object>() {
        @Override
        public void reset( Object object) {
          // Do nothing.
        }
      };

  private FactoryPools() {}


  @NotNull
  public static <T extends Poolable> Pool<T> simple(int size, @NotNull Factory<T> factory) {
    return build(new SimplePool<T>(size), factory);
  }


  @NotNull
  public static <T extends Poolable> Pool<T> threadSafe(int size, @NotNull Factory<T> factory) {
    return build(new SynchronizedPool<T>(size), factory);
  }


  @NotNull
  public static <T> Pool<List<T>> threadSafeList() {
    return threadSafeList(DEFAULT_POOL_SIZE);
  }

  /**
   * Returns a new thread safe {@link Pool} that never returns {@code null} and that contains {@link
   * List Lists} of a specific generic type with the given maximum size.
   *
   * <p>If the pool is empty when {@link Pool#acquire()} is called, a new {@link List} will be
   * created.
   *
   * @param <T> The type of object that the {@link List Lists} will contain.
   * @return pool
   */
  // Public API.
  @SuppressWarnings("WeakerAccess")
  @NotNull
  public static <T> Pool<List<T>> threadSafeList(int size) {
    return build(
        new SynchronizedPool<List<T>>(size),
        new Factory<List<T>>() {
          
          @Override
          public List<T> create() {
            return new ArrayList<>();
          }
        },
        new Resetter<List<T>>() {
          @Override
          public void reset( List<T> object) {
            object.clear();
          }
        });
  }

  @NotNull
  private static <T extends Poolable> Pool<T> build(
       @NotNull Pool<T> pool, @NotNull Factory<T> factory) {
    return build(pool, factory, FactoryPools.<T>emptyResetter());
  }

  @NotNull
  private static <T> Pool<T> build(
     @NotNull Pool<T> pool, @NotNull Factory<T> factory, @NotNull Resetter<T> resetter) {
    return new FactoryPool<>(pool, factory, resetter);
  }

  @NotNull
  @SuppressWarnings("unchecked")
  private static <T> Resetter<T> emptyResetter() {
    return (Resetter<T>) EMPTY_RESETTER;
  }

  /**
   * Creates new instances of the given type.
   *
   * @param <T> The type of Object that will be created.
   */
  public interface Factory<T> {
    T create();
  }

  /**
   * Resets state when objects are returned to the pool.
   *
   * @param <T> The type of Object that will be reset.
   */
  public interface Resetter<T> {
    void reset(@NotNull T object);
  }

  /**
   * Allows additional verification to catch errors caused by using objects while they are in an
   * object pool.
   */
  public interface Poolable {

    @NotNull
    StateVerifier getVerifier();
  }

  private static final class FactoryPool<T> implements Pool<T> {
    private final Factory<T> factory;
    private final Resetter<T> resetter;
    private final Pool<T> pool;

    FactoryPool(@NotNull Pool<T> pool,@NotNull  Factory<T> factory, @NotNull Resetter<T> resetter) {
      this.pool = pool;
      this.factory = factory;
      this.resetter = resetter;
    }

    @Override
    public T acquire() {
      T result = pool.acquire();
      if (result == null) {
        result = factory.create();
        if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
          LogUtil.info(TAG, "Created new " + result.getClass());
        }
      }
      if (result instanceof Poolable) {
        ((Poolable) result).getVerifier().setRecycled(false /*isRecycled*/);
      }
      return result;
    }

    @Override
    public boolean release(@NotNull T instance) {
      if (instance instanceof Poolable) {
        ((Poolable) instance).getVerifier().setRecycled(true /*isRecycled*/);
      }
      resetter.reset(instance);
      return pool.release(instance);
    }
  }
}
